package rexsee.core.widget;

import java.util.List;

import android.content.Context;
import android.graphics.Rect;
import android.view.FocusFinder;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.Scroller;

public class HScrollView extends FrameLayout {

	private static final int ANIMATED_SCROLL_GAP = 250;
	private static final float MAX_SCROLL_FACTOR = 0.5f;

	private long mLastScroll;

	private final Rect mTempRect = new Rect();
	private Scroller mScroller;
	private float mLastMotionX;

	private boolean mIsLayoutDirty = true;
	private View mChildToScrollTo = null;
	private boolean mIsBeingDragged = false;
	private VelocityTracker mVelocityTracker;
	private final boolean mSmoothScrollingEnabled = true;

	private int mTouchSlop;
	private int mMinimumVelocity;
	private int mMaximumVelocity;

	private int mActivePointerId = INVALID_POINTER;
	private static final int INVALID_POINTER = -1;

	public HScrollView(Context context) {
		super(context);
		initScrollView();
	}
	private void initScrollView() {
		mScroller = new Scroller(getContext());
		setFocusable(true);
		setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
		//setWillNotDraw(false);
		final ViewConfiguration configuration = ViewConfiguration.get(getContext());
		mTouchSlop = configuration.getScaledTouchSlop();
		mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
		mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
	}

	@Override
	protected float getLeftFadingEdgeStrength() {
		if (getChildCount() == 0) { return 0.0f; }
		final int length = getHorizontalFadingEdgeLength();
		if (getScrollX() < length) { return getScrollX() / (float) length; }
		return 1.0f;
	}

	@Override
	protected float getRightFadingEdgeStrength() {
		if (getChildCount() == 0) { return 0.0f; }
		final int length = getHorizontalFadingEdgeLength();
		final int rightEdge = getWidth() - getPaddingRight();
		final int span = getChildAt(0).getRight() - getScrollX() - rightEdge;
		if (span < length) { return span / (float) length; }
		return 1.0f;
	}

	@Override
	protected int computeHorizontalScrollRange() {
		final int count = getChildCount();
		final int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();
		if (count == 0) { return contentWidth; }

		int scrollRange = getChildAt(0).getRight();
		final int scrollX = getScrollX();
		final int overscrollRight = Math.max(0, scrollRange - contentWidth);
		if (scrollX < 0) {
			scrollRange -= scrollX;
		} else if (scrollX > overscrollRight) {
			scrollRange += scrollX - overscrollRight;
		}

		return scrollRange;
	}

	@Override
	protected int computeHorizontalScrollOffset() {
		return Math.max(0, super.computeHorizontalScrollOffset());
	}

	@Override
	protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
		ViewGroup.LayoutParams lp = child.getLayoutParams();

		int childWidthMeasureSpec;
		int childHeightMeasureSpec;

		childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, getPaddingTop()
				+ getPaddingBottom(), lp.height);

		childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

		child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
	}

	@Override
	protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
			int parentHeightMeasureSpec, int heightUsed) {
		final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

		final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
				getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin
						+ heightUsed, lp.height);
		final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
				lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);

		child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
	}

	@Override
	public void computeScroll() {
		if (mScroller.computeScrollOffset()) {
			int oldX = getScrollX();
			int oldY = getScrollY();
			int x = mScroller.getCurrX();
			int y = mScroller.getCurrY();
			if (oldX != x || oldY != y) {
				scrollBy(x - oldX, y - oldY);
				onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);

			}
			awakenScrollBars();
			postInvalidate();
		}
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		if (widthMode == MeasureSpec.UNSPECIFIED) { return; }

		if (getChildCount() > 0) {
			final View child = getChildAt(0);
			int width = getMeasuredWidth();
			if (child.getMeasuredWidth() < width) {
				final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();

				int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom(), lp.height);
				width -= getPaddingLeft();
				width -= getPaddingRight();
				int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);

				child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
			}
		}
	}

	@Override
	public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
		if (disallowIntercept) {
			recycleVelocityTracker();
		}
		super.requestDisallowInterceptTouchEvent(disallowIntercept);
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		final int action = ev.getAction();
		if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { return true; }

		switch (action & MotionEvent.ACTION_MASK) {
			case MotionEvent.ACTION_MOVE : {
				final int activePointerId = mActivePointerId;
				if (activePointerId == INVALID_POINTER) {
					// If we don't have a valid id, the touch down wasn't on content.
					break;
				}

				final int pointerIndex = ev.findPointerIndex(activePointerId);
				final float x = ev.getX(pointerIndex);
				final int xDiff = (int) Math.abs(x - mLastMotionX);
				if (xDiff > mTouchSlop) {
					mIsBeingDragged = true;
					mLastMotionX = x;
					initVelocityTrackerIfNotExists();
					mVelocityTracker.addMovement(ev);
					if (getParent() != null) getParent().requestDisallowInterceptTouchEvent(true);
				}
				break;
			}

			case MotionEvent.ACTION_DOWN : {
				final float x = ev.getX();
				if (!inChild((int) x, (int) ev.getY())) {
					mIsBeingDragged = false;
					recycleVelocityTracker();
					break;
				}

				mLastMotionX = x;
				mActivePointerId = ev.getPointerId(0);

				initOrResetVelocityTracker();
				mVelocityTracker.addMovement(ev);

				mIsBeingDragged = !mScroller.isFinished();
				break;
			}

			case MotionEvent.ACTION_CANCEL :
			case MotionEvent.ACTION_UP :
				mIsBeingDragged = false;
				mActivePointerId = INVALID_POINTER;
				invalidate();
				break;
			case MotionEvent.ACTION_POINTER_DOWN : {
				final int index = ev.getActionIndex();
				mLastMotionX = ev.getX(index);
				mActivePointerId = ev.getPointerId(index);
				break;
			}
			case MotionEvent.ACTION_POINTER_UP :
				onSecondaryPointerUp(ev);
				mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
				break;
		}

		return mIsBeingDragged;
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		initVelocityTrackerIfNotExists();
		mVelocityTracker.addMovement(ev);

		final int action = ev.getAction();

		switch (action & MotionEvent.ACTION_MASK) {
			case MotionEvent.ACTION_DOWN : {
				mIsBeingDragged = getChildCount() != 0;
				if (!mIsBeingDragged) { return false; }

				if (!mScroller.isFinished()) {
					mScroller.abortAnimation();
				}

				// Remember where the motion event started
				mLastMotionX = ev.getX();
				mActivePointerId = ev.getPointerId(0);
				break;
			}
			case MotionEvent.ACTION_MOVE :
				if (mIsBeingDragged) {
					// Scroll to follow the motion event
					final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
					final float x = ev.getX(activePointerIndex);
					final int deltaX = (int) (mLastMotionX - x);
					mLastMotionX = x;

					final int oldX = getScrollX();
					final int oldY = getScrollY();
					scrollBy(deltaX, 0);
					onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);

				}
				break;
			case MotionEvent.ACTION_UP :
				if (mIsBeingDragged) {
					final VelocityTracker velocityTracker = mVelocityTracker;
					velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
					int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);

					if (getChildCount() > 0) {
						if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
							fling(-initialVelocity);
						} else {
							invalidate();
						}
					}

					mActivePointerId = INVALID_POINTER;
					mIsBeingDragged = false;
					recycleVelocityTracker();

				}
				break;
			case MotionEvent.ACTION_CANCEL :
				if (mIsBeingDragged && getChildCount() > 0) {
					invalidate();
					mActivePointerId = INVALID_POINTER;
					mIsBeingDragged = false;
					recycleVelocityTracker();

				}
				break;
			case MotionEvent.ACTION_POINTER_UP :
				onSecondaryPointerUp(ev);
				break;
		}
		return true;
	}

	@Override
	public void requestChildFocus(View child, View focused) {
		if (!mIsLayoutDirty) {
			scrollToChild(focused);
		} else {
			mChildToScrollTo = focused;
		}
		super.requestChildFocus(child, focused);
	}

	@Override
	protected boolean onRequestFocusInDescendants(int direction,
			Rect previouslyFocusedRect) {

		if (direction == View.FOCUS_FORWARD) {
			direction = View.FOCUS_RIGHT;
		} else if (direction == View.FOCUS_BACKWARD) {
			direction = View.FOCUS_LEFT;
		}

		final View nextFocus = previouslyFocusedRect == null ?
				FocusFinder.getInstance().findNextFocus(this, null, direction) :
				FocusFinder.getInstance().findNextFocusFromRect(this,
						previouslyFocusedRect, direction);

		if (nextFocus == null) { return false; }

		if (isOffScreen(nextFocus)) { return false; }

		return nextFocus.requestFocus(direction, previouslyFocusedRect);
	}

	@Override
	public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
		rectangle.offset(child.getLeft() - child.getScrollX(), child.getTop() - child.getScrollY());
		return scrollToChildRect(rectangle, immediate);
	}

	@Override
	public void requestLayout() {
		mIsLayoutDirty = true;
		super.requestLayout();
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		mIsLayoutDirty = false;
		if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
			scrollToChild(mChildToScrollTo);
		}
		mChildToScrollTo = null;
		scrollTo(getScrollX(), getScrollY());
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		super.onSizeChanged(w, h, oldw, oldh);
		View currentFocused = findFocus();
		if (null == currentFocused || this == currentFocused) return;

		final int maxJump = getRight() - getLeft();

		if (isWithinDeltaOfScreen(currentFocused, maxJump)) {
			currentFocused.getDrawingRect(mTempRect);
			offsetDescendantRectToMyCoords(currentFocused, mTempRect);
			int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
			doScrollX(scrollDelta);
		}
	}
	@Override
	public void scrollTo(int x, int y) {
		if (getChildCount() > 0) {
			View child = getChildAt(0);
			x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth());
			y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());
			if (x != getScrollX() || y != getScrollY()) {
				super.scrollTo(x, y);
			}
		}
	}
	/*
	*/

	private boolean inChild(int x, int y) {
		if (getChildCount() > 0) {
			final int scrollX = getScrollX();
			final View child = getChildAt(0);
			return !(y < child.getTop()
					|| y >= child.getBottom()
					|| x < child.getLeft() - scrollX || x >= child.getRight() - scrollX);
		}
		return false;
	}

	private void initOrResetVelocityTracker() {
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		} else {
			mVelocityTracker.clear();
		}
	}

	private void initVelocityTrackerIfNotExists() {
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
	}

	private void recycleVelocityTracker() {
		if (mVelocityTracker != null) {
			mVelocityTracker.recycle();
			mVelocityTracker = null;
		}
	}

	public int getMaxScrollAmount() {
		return (int) (MAX_SCROLL_FACTOR * (getRight() - getLeft()));
	}

	private void onSecondaryPointerUp(MotionEvent ev) {
		final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
				MotionEvent.ACTION_POINTER_INDEX_SHIFT;
		final int pointerId = ev.getPointerId(pointerIndex);
		if (pointerId == mActivePointerId) {
			// This was our active pointer going up. Choose a new
			// active pointer and adjust accordingly.
			// TODO: Make this decision more intelligent.
			final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
			mLastMotionX = ev.getX(newPointerIndex);
			mActivePointerId = ev.getPointerId(newPointerIndex);
			if (mVelocityTracker != null) {
				mVelocityTracker.clear();
			}
		}
	}

	public boolean pageScroll(int direction) {
		boolean right = direction == View.FOCUS_RIGHT;
		int width = getWidth();

		if (right) {
			mTempRect.left = getScrollX() + width;
			int count = getChildCount();
			if (count > 0) {
				View view = getChildAt(0);
				if (mTempRect.left + width > view.getRight()) {
					mTempRect.left = view.getRight() - width;
				}
			}
		} else {
			mTempRect.left = getScrollX() - width;
			if (mTempRect.left < 0) {
				mTempRect.left = 0;
			}
		}
		mTempRect.right = mTempRect.left + width;

		return scrollAndFocus(direction, mTempRect.left, mTempRect.right);
	}

	public boolean fullScroll(int direction) {
		boolean right = direction == View.FOCUS_RIGHT;
		int width = getWidth();

		mTempRect.left = 0;
		mTempRect.right = width;

		if (right) {
			int count = getChildCount();
			if (count > 0) {
				View view = getChildAt(0);
				mTempRect.right = view.getRight();
				mTempRect.left = mTempRect.right - width;
			}
		}

		return scrollAndFocus(direction, mTempRect.left, mTempRect.right);
	}

	private boolean scrollAndFocus(int direction, int left, int right) {
		boolean handled = true;

		int width = getWidth();
		int containerLeft = getScrollX();
		int containerRight = containerLeft + width;
		boolean goLeft = direction == View.FOCUS_LEFT;

		View newFocused = findFocusableViewInBounds(goLeft, left, right);
		if (newFocused == null) {
			newFocused = this;
		}

		if (left >= containerLeft && right <= containerRight) {
			handled = false;
		} else {
			int delta = goLeft ? (left - containerLeft) : (right - containerRight);
			doScrollX(delta);
		}

		if (newFocused != findFocus()) newFocused.requestFocus(direction);

		return handled;
	}

	public boolean arrowScroll(int direction) {

		View currentFocused = findFocus();
		if (currentFocused == this) currentFocused = null;

		View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);

		final int maxJump = getMaxScrollAmount();

		if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump)) {
			nextFocused.getDrawingRect(mTempRect);
			offsetDescendantRectToMyCoords(nextFocused, mTempRect);
			int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
			doScrollX(scrollDelta);
			nextFocused.requestFocus(direction);
		} else {
			// no new focus
			int scrollDelta = maxJump;

			if (direction == View.FOCUS_LEFT && getScrollX() < scrollDelta) {
				scrollDelta = getScrollX();
			} else if (direction == View.FOCUS_RIGHT && getChildCount() > 0) {

				int daRight = getChildAt(0).getRight();

				int screenRight = getScrollX() + getWidth();

				if (daRight - screenRight < maxJump) {
					scrollDelta = daRight - screenRight;
				}
			}
			if (scrollDelta == 0) { return false; }
			doScrollX(direction == View.FOCUS_RIGHT ? scrollDelta : -scrollDelta);
		}

		if (currentFocused != null && currentFocused.isFocused()
				&& isOffScreen(currentFocused)) {
			final int descendantFocusability = getDescendantFocusability(); // save
			setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
			requestFocus();
			setDescendantFocusability(descendantFocusability); // restore
		}
		return true;
	}

	private boolean isOffScreen(View descendant) {
		return !isWithinDeltaOfScreen(descendant, 0);
	}

	private boolean isWithinDeltaOfScreen(View descendant, int delta) {
		descendant.getDrawingRect(mTempRect);
		offsetDescendantRectToMyCoords(descendant, mTempRect);

		return (mTempRect.right + delta) >= getScrollX()
				&& (mTempRect.left - delta) <= (getScrollX() + getWidth());
	}

	private void doScrollX(int delta) {
		if (delta != 0) {
			if (mSmoothScrollingEnabled) {
				smoothScrollBy(delta, 0);
			} else {
				scrollBy(delta, 0);
			}
		}
	}

	public final void smoothScrollBy(int dx, int dy) {
		if (getChildCount() == 0) {
			// Nothing to do.
			return;
		}
		long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
		if (duration > ANIMATED_SCROLL_GAP) {
			final int width = getWidth() - getPaddingRight() - getPaddingLeft();
			final int right = getChildAt(0).getWidth();
			final int maxX = Math.max(0, right - width);
			final int scrollX = getScrollX();
			dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX;

			mScroller.startScroll(scrollX, getScrollY(), dx, 0);
			invalidate();
		} else {
			if (!mScroller.isFinished()) {
				mScroller.abortAnimation();
			}
			scrollBy(dx, dy);
		}
		mLastScroll = AnimationUtils.currentAnimationTimeMillis();
	}

	public final void smoothScrollTo(int x, int y) {
		smoothScrollBy(x - getScrollX(), y - getScrollY());
	}

	private void scrollToChild(View child) {
		child.getDrawingRect(mTempRect);
		offsetDescendantRectToMyCoords(child, mTempRect);
		int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
		if (scrollDelta != 0) {
			scrollBy(scrollDelta, 0);
		}
	}

	private boolean scrollToChildRect(Rect rect, boolean immediate) {
		final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
		final boolean scroll = delta != 0;
		if (scroll) {
			if (immediate) {
				scrollBy(delta, 0);
			} else {
				smoothScrollBy(delta, 0);
			}
		}
		return scroll;
	}

	protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
		if (getChildCount() == 0) return 0;

		int width = getWidth();
		int screenLeft = getScrollX();
		int screenRight = screenLeft + width;

		int fadingEdge = getHorizontalFadingEdgeLength();

		// leave room for left fading edge as long as rect isn't at very left
		if (rect.left > 0) {
			screenLeft += fadingEdge;
		}

		// leave room for right fading edge as long as rect isn't at very right
		if (rect.right < getChildAt(0).getWidth()) {
			screenRight -= fadingEdge;
		}

		int scrollXDelta = 0;

		if (rect.right > screenRight && rect.left > screenLeft) {
			// need to move right to get it in view: move right just enough so
			// that the entire rectangle is in view (or at least the first
			// screen size chunk).

			if (rect.width() > width) {
				// just enough to get screen size chunk on
				scrollXDelta += (rect.left - screenLeft);
			} else {
				// get entire rect at right of screen
				scrollXDelta += (rect.right - screenRight);
			}

			// make sure we aren't scrolling beyond the end of our content
			int right = getChildAt(0).getRight();
			int distanceToRight = right - screenRight;
			scrollXDelta = Math.min(scrollXDelta, distanceToRight);

		} else if (rect.left < screenLeft && rect.right < screenRight) {
			// need to move right to get it in view: move right just enough so that
			// entire rectangle is in view (or at least the first screen
			// size chunk of it).

			if (rect.width() > width) {
				// screen size chunk
				scrollXDelta -= (screenRight - rect.right);
			} else {
				// entire rect at left
				scrollXDelta -= (screenLeft - rect.left);
			}

			// make sure we aren't scrolling any further than the left our content
			scrollXDelta = Math.max(scrollXDelta, -getScrollX());
		}
		return scrollXDelta;
	}

	private View findFocusableViewInMyBounds(final boolean leftFocus, final int left, View preferredFocusable) {
		final int fadingEdgeLength = getHorizontalFadingEdgeLength() / 2;
		final int leftWithoutFadingEdge = left + fadingEdgeLength;
		final int rightWithoutFadingEdge = left + getWidth() - fadingEdgeLength;

		if ((preferredFocusable != null)
				&& (preferredFocusable.getLeft() < rightWithoutFadingEdge)
				&& (preferredFocusable.getRight() > leftWithoutFadingEdge)) { return preferredFocusable; }

		return findFocusableViewInBounds(leftFocus, leftWithoutFadingEdge,
				rightWithoutFadingEdge);
	}

	private View findFocusableViewInBounds(boolean leftFocus, int left, int right) {

		List<View> focusables = getFocusables(View.FOCUS_FORWARD);
		View focusCandidate = null;

		boolean foundFullyContainedFocusable = false;

		int count = focusables.size();
		for (int i = 0; i < count; i++) {
			View view = focusables.get(i);
			int viewLeft = view.getLeft();
			int viewRight = view.getRight();

			if (left < viewRight && viewLeft < right) {

				final boolean viewIsFullyContained = (left < viewLeft) &&
						(viewRight < right);

				if (focusCandidate == null) {
					focusCandidate = view;
					foundFullyContainedFocusable = viewIsFullyContained;
				} else {
					final boolean viewIsCloserToBoundary =
							(leftFocus && viewLeft < focusCandidate.getLeft()) ||
									(!leftFocus && viewRight > focusCandidate.getRight());

					if (foundFullyContainedFocusable) {
						if (viewIsFullyContained && viewIsCloserToBoundary) {
							focusCandidate = view;
						}
					} else {
						if (viewIsFullyContained) {
							focusCandidate = view;
							foundFullyContainedFocusable = true;
						} else if (viewIsCloserToBoundary) {
							focusCandidate = view;
						}
					}
				}
			}
		}

		return focusCandidate;
	}

	private boolean isViewDescendantOf(View child, View parent) {
		if (child == parent) { return true; }

		final ViewParent theParent = child.getParent();
		return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
	}

	public void fling(int velocityX) {
		if (getChildCount() > 0) {
			int width = getWidth() - getPaddingRight() - getPaddingLeft();
			int right = getChildAt(0).getWidth();

			mScroller.fling(getScrollX(), getScrollY(), velocityX, 0, 0, Math.max(0, right - width), 0, 0);

			final boolean movingRight = velocityX > 0;

			View currentFocused = findFocus();
			View newFocused = findFocusableViewInMyBounds(movingRight,
					mScroller.getFinalX(), currentFocused);

			if (newFocused == null) {
				newFocused = this;
			}

			if (newFocused != currentFocused) {
				newFocused.requestFocus(movingRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT);
			}

			invalidate();
		}
	}

	private int clamp(int n, int my, int child) {
		if (my >= child || n < 0) { return 0; }
		if ((my + n) > child) { return child - my; }
		return n;
	}
}
